package cn.seaboot.commons.http;

import cn.seaboot.commons.core.CommonUtils;
import cn.seaboot.commons.core.Converter;
import cn.seaboot.commons.core.FastJsonUtils;
import org.apache.hc.client5.http.ClientProtocolException;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.entity.mime.HttpMultipartMode;
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicNameValuePair;
import org.apache.hc.core5.http.protocol.HttpContext;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 逻辑更加简单的 http-request 工具
 * <p>
 * 提供方便使用、便于理解的 API，
 * 限定代码的行为，避免使用一些老旧过时的代码
 *
 * @author Mr.css
 * @version 2024-03-04 15:01
 */
public class HttpRequest extends HttpUriRequestBase {
    private static final long serialVersionUID = 4545177632066762089L;
    protected final CloseableHttpClient httpClient;

    public HttpRequest(CloseableHttpClient httpClient, String method, URI requestUri) {
        super(method, requestUri);
        this.httpClient = httpClient;
    }

    /**
     * 设置 post 请求的参数
     * <p>
     * 由于底层源码的限制，不提供设置单个参数的函数
     *
     * @param params 请求参数
     */
    public void setParameters(Map<String, Object> params, Charset charset) {
        if (CommonUtils.isNotEmpty(params)) {
            List<NameValuePair> pairList = new ArrayList<>(params.size());
            for (Map.Entry<String, ?> entry : params.entrySet()) {
                pairList.add(new BasicNameValuePair(entry.getKey(), Converter.toString(entry.getValue())));
            }
            super.setEntity(new UrlEncodedFormEntity(pairList, charset));
        }
    }

    /**
     * 添加一个二进制文件，通常用于发送文件
     * <p>
     * HttpMultipartMode 的说明如下：
     * <p>
     * LEGACY：
     * <p>
     * 传统兼容模式意味着在处理多部分数据时，只生成最基本的字段。
     * 这些字段通常包括 Content-Type（指定内容的 MIME 类型）和
     * Content-Disposition（描述如何处理内容，例如作为附件或内联显示）。
     * 这种模式旨在与旧系统或不太严格的解析器兼容。
     * <p>
     * STRICT：
     * <p>
     * 严格MIME规范遵循模式意味着在处理多部分数据时，将严格遵循MIME类型的规范。
     * <p>
     * EXTENDED
     * <p>
     * 扩展MIME规范遵循模式在严格遵循MIME规范的基础上，提供了对国际化字符的支持。
     * 在这种模式下，头部字段的值可以包含使用UTF-8编码的国际字符。
     * 这对于需要在多部分消息中包含非ASCII字符（如中文、日文等）的应用程序来说非常有用
     *
     * @param filename inputStream name
     * @param is       is
     * @see MultipartEntityBuilder
     */
    public void setBinaryEntity(final String filename, final InputStream is) {
        MultipartEntityBuilder builder = MultipartEntityBuilder
                .create()
                .setMode(HttpMultipartMode.LEGACY)
                .addBinaryBody(filename, is, ContentType.DEFAULT_BINARY, filename);
        super.setEntity(builder.build());
    }

    /**
     * 设置请求头，如果已经存在同名的，则直接覆盖
     * <p>
     * Overwrites the first header with the same name. The new header will be appended to
     * the end of the list, if no header with the given name can be found.
     *
     * @param value the value of the header.
     */
    public void setContentType(String value) {
        super.setHeader("Content-Type", value);
    }

    /**
     * 发送一份纯文本内容，后台需要用  {@code RequestBody} 接收，发送复杂格式的数据，会用到这个函数。
     *
     * @param type    mine 类型
     * @param content request-body
     */
    public void setBody(ContentType type, String content) {
        super.setHeader("Content-Type", type.toString());
        super.setEntity(new StringEntity(content, type));
    }

    /**
     * Send request body with a json.
     * <p>
     * 发送一份 xml 数据，后台需要用  {@code RequestBody} 接收，发送复杂格式的数据，会用到这个函数。
     *
     * @param content a xml
     */
    public void setXmlBody(String content) {
        super.setHeader("Content-Type", "text/xml; charset=UTF-8");
        super.setEntity(new StringEntity(content, ContentType.TEXT_XML));
    }

    /**
     * Send request body with a json.
     * <p>
     * 发送一份 json 数据，后台需要用  {@code RequestBody} 接收，发送复杂格式的数据，会用到这个函数。
     *
     * @param content a json
     */
    public void setJsonBody(String content) {
        super.setHeader("Content-Type", "application/json; charset=UTF-8");
        super.setEntity(new StringEntity(content, ContentType.APPLICATION_JSON));
    }

    /**
     * Send request body with a json.
     * <p>
     * 发送一份 json 数据，后台需要用  {@code RequestBody} 接收，发送复杂格式的数据，会用到这个函数。
     *
     * @param content a json
     */
    public void setJsonBody(Object content) {
        super.setHeader("Content-Type", "application/json; charset=UTF-8");
        String json = FastJsonUtils.toJSONString(content);
        super.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
    }

    /**
     * 提交一个请求，并通过回调函数返回后台数据响应
     * <p>
     * execute a request
     *
     * @throws IOException -
     */
    public <T> T execute(final HttpClientResponseHandler<T> responseHandler) throws IOException {
        return this.httpClient.execute(this, responseHandler);
    }

    /**
     * 提交一个请求，并通过回调函数返回后台数据响应
     *
     * @param responseHandler the response handler
     * @param context         the context to use for the execution, or
     *                        {@code null} to use the default context
     * @return the response object as generated by the response handler.
     * @throws IOException             in case of a problem or the connection was aborted
     * @throws ClientProtocolException in case of an http protocol error
     */
    public <T> T execute(
            final HttpContext context,
            final HttpClientResponseHandler<? extends T> responseHandler) throws IOException {
        return this.httpClient.execute(this, context, responseHandler);
    }

    /**
     * 提交一个请求，并通过回调函数返回后台数据响应
     *
     * @param target          the target host for the request.
     *                        Implementations may accept {@code null}
     *                        if they can still determine a route, for example
     *                        to a default target or by inspecting the request.
     * @param context         the context to use for the execution, or
     *                        {@code null} to use the default context
     * @param responseHandler the response handler
     * @throws IOException -
     */
    public <T> T execute(final HttpHost target,
                         final HttpContext context,
                         final HttpClientResponseHandler<? extends T> responseHandler) throws IOException {
        return this.httpClient.execute(target, this, context, responseHandler);
    }
}
